/*
 * Copyright (c) 2010, Nikolaus Moll
 * All rights reserved.
 */
package net.miblounge.gradecalculator.guicommon.widgets;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import javax.swing.event.EventListenerList;

import net.miblounge.gradecalculator.guicommon.exceptions.MultiColumnTreeModelException;
import net.miblounge.gradecalculator.guicommon.interfaces.IContextMenuListener;
import net.miblounge.gradecalculator.guicommon.interfaces.IMultiColumnTreeModelListener;
import net.miblounge.gradecalculator.guicommon.interfaces.IMutliColumnTreeEventListener;
import net.miblounge.gradecalculator.guicommon.models.AbstractMultiColumnTreeModel;

import com.trolltech.qt.core.QByteArray;
import com.trolltech.qt.core.QMimeData;
import com.trolltech.qt.core.QModelIndex;
import com.trolltech.qt.core.QPoint;
import com.trolltech.qt.core.QTimer;
import com.trolltech.qt.core.Qt;
import com.trolltech.qt.gui.QAbstractItemDelegate;
import com.trolltech.qt.gui.QApplication;
import com.trolltech.qt.gui.QBrush;
import com.trolltech.qt.gui.QContextMenuEvent;
import com.trolltech.qt.gui.QDrag;
import com.trolltech.qt.gui.QDragEnterEvent;
import com.trolltech.qt.gui.QDragMoveEvent;
import com.trolltech.qt.gui.QDropEvent;
import com.trolltech.qt.gui.QFont;
import com.trolltech.qt.gui.QLineEdit;
import com.trolltech.qt.gui.QMessageBox;
import com.trolltech.qt.gui.QMouseEvent;
import com.trolltech.qt.gui.QTreeWidget;
import com.trolltech.qt.gui.QTreeWidgetItem;
import com.trolltech.qt.gui.QWidget;

public class MultiColumnTree extends QTreeWidget implements IMultiColumnTreeModelListener {

	private static final int AUTOEXPANDDELAY = 300;
	private static final int AUTOCLOSEDELAY = 1250;
	
	private AbstractMultiColumnTreeModel model;
	private EventListenerList listeners = new EventListenerList();
	
	private TreeIndex currentEditorIndex = null;
	private QTreeWidgetItem currentEditorItem = null;
	private int currentEditorColumn = 0;
	private QPoint dragStartPosition;
	
	private TreeIndex draggingIndex = null;
	private Object draggingObject = null;
	private final QTimer timer;
	private QTreeWidgetItem mouseOverItem;
	
	
	public MultiColumnTree(final QWidget parent)
	{
		super(parent);
		setHeaderLabel("");

		timer = new QTimer();
		timer.timeout.connect(this, "timeout()");
		
		this.currentItemChanged.connect(this, "currentItemChangedEvent(QTreeWidgetItem, QTreeWidgetItem)");
		this.itemDoubleClicked.connect(this, "doubleClickEvent(QTreeWidgetItem, int)");
		this.itemDelegate().closeEditor.connect(this, "closeInlineEditorEvent(QWidget, QAbstractItemDelegate$EndEditHint)");
	}
	
	public void setModel(final AbstractMultiColumnTreeModel model) {
		if (this.model != null) {
			this.model.removeTreeEventListener(this);
		}
		
		this.clear();
		this.model = model;

		setDragEnabled(model.isDragEnabled());
		setAcceptDrops(model.isDropEnabled());
		setDropIndicatorShown(model.isDropIndicatorShown());
		
		model.addTreeEventListener(this);
		setHeaderLabels(model.getHeaderLabels());
		
		final TreeIndex index = null;
		final QTreeWidgetItem parentItem = null;
		final int rowCount = model.getRowCount(index);

		for (int row = 0; row < rowCount; row++) {
			addTreeWidgetItem(parentItem, null, row);
		}
		
		for (int i = 0; i < columnCount(); i++) {
			resizeColumnToContents(i);
		}
		if (columnWidth(0) < 200) {
			setColumnWidth(0, 200);
		}
	}
	
	public void addTreeEventListener(final IMutliColumnTreeEventListener listener) 
	{ 
		this.listeners.add(IMutliColumnTreeEventListener.class, listener);
	} 

	public void addContextMenuListener(final IContextMenuListener listener) 
	{ 
		this.listeners.add(IContextMenuListener.class, listener);
	} 

	protected synchronized void notifyEditRequest(final TreeIndex index, final int column, final Object data) 
	{ 
		for (IMutliColumnTreeEventListener l : this.listeners.getListeners(IMutliColumnTreeEventListener.class)) {
			l.treeEditRequestEvent(index, column, data);
		}
	}
	
	protected synchronized void notifySelectionChanged(final TreeIndex index,final Object data) 
	{ 
		for (IMutliColumnTreeEventListener l : this.listeners.getListeners(IMutliColumnTreeEventListener.class)) {
			l.treeSelectionChangedEvent(index, data);
		}
	}
	
	protected synchronized void notifyContextMenuTriggered(final QPoint position) 
	{ 
		for (IContextMenuListener l : this.listeners.getListeners(IContextMenuListener.class)) {
			l.contextMenuTriggeredEvent(position);
		}
	}
	
	public void startInlineEditor(final TreeIndex index, final int column)
	{
		closeInlineEditor();

		currentEditorIndex = index;
		currentEditorItem = getWidgetItem(index);
		currentEditorColumn = column;
		openPersistentEditor(currentEditorItem, currentEditorColumn);
	}

	private void closeInlineEditor()
	{
		if (currentEditorItem == null) {
			return;
		}
		
		final QTreeWidgetItem item = currentEditorItem;
		currentEditorItem = null;

		closePersistentEditor(item, currentEditorColumn);
		currentEditorIndex = null;
	}

	protected void currentItemChangedEvent(final QTreeWidgetItem item, final QTreeWidgetItem previous)
	{
		if (item == null) {
			notifySelectionChanged(null, null);
		} else {
			if (this.updatesEnabled()) {
				final TreeIndex index = getTreeIndex(item);
				final Object data = model.getData(index);
				notifySelectionChanged(index, data);
			}
		}
	}
	
	protected void closeInlineEditorEvent(final QWidget editor, final QAbstractItemDelegate.EndEditHint hint)
	{
		if (currentEditorItem == null) {
			return;
		}

		final TreeIndex index = currentEditorIndex;
		final int column = currentEditorColumn;
		
		try {
			final String text = ((QLineEdit)editor).text().trim();
			closeInlineEditor();
			model.setText(index, column, text);
		}
		catch (final MultiColumnTreeModelException exception)
		{
			closeInlineEditor();
			recursiveUpdate(index);
			QMessageBox.critical(this.parentWidget(), "Error", exception.getMessage());
		}
	}
	
	protected void doubleClickEvent(final QTreeWidgetItem item, final int column) {
		final TreeIndex index = getTreeIndex(item);
		if (item.childCount() == 0) {
			notifyEditRequest(index, column, model.getData(index));
		}
	}
	
	private void updateIndex(final TreeIndex index, final QTreeWidgetItem item)
	{
		final Object data = model.getData(index);
		final QFont font = model.getFont(data);
		final QBrush brush = model.getBrush(data);
		
		final int colCount = model.getColumnCount();
		for (int col = 0; col < colCount; col++) {
			item.setText(col, tr(model.getText(data, col)));
			item.setFont(col, font);
        	item.setForeground(col, brush);
        	item.setTextAlignment(col, model.getAlignment(col));
		}		
	}
	
	private QTreeWidgetItem addTreeWidgetItem(final QTreeWidgetItem parent, final TreeIndex parentIndex, final int row)
	{
		final TreeIndex index = new TreeIndex(row, parentIndex);

		final QTreeWidgetItem item = new QTreeWidgetItem();

		updateIndex(index, item);
		
		final int rowCount = model.getRowCount(index);
		for (int childRow = 0; childRow < rowCount; childRow++) {
			addTreeWidgetItem(item, index, childRow);
		}

		if (parent == null) {
			insertTopLevelItem(row, item);
		} else {
			parent.insertChild(row, item);
		}
		
		return item;
	}

	private QTreeWidgetItem getChildWidgetItem(final QTreeWidgetItem parent, final int index)
	{
		if (parent == null) {
			return this.topLevelItem(index);
		} else {
			return parent.child(index);
		}
	}
	
	public Object getSelectedItem()
	{
		return model.getData(getCurrentTreeIndex());
	}
	
	public void recursiveUpdate(final TreeIndex index)
	{
		if (index == null) {
			return;
		}
		
//		final TreeIndex parentIndex = index.getParent();
		
		TreeIndex current = index;
		QTreeWidgetItem item = getWidgetItem(index);
		if (item == null) {
			return;
		}
		while (current != null) {
			updateIndex(current, item);	

			current = current.getParent();
			item = item.parent();
		}
	}

	@Override
	public void dataChangedEvent(final TreeIndex row) {
		recursiveUpdate(row);
	}
	
	@Override
	public void dataChangedEvent(final TreeIndex row, final int column, final String newText) {
		final Stack<TreeIndex> stack = new Stack<TreeIndex>();
		
		TreeIndex currentRow = row;
		while (currentRow != null) {
			stack.push(currentRow);
			currentRow = currentRow.getParent();
		}
		
		QTreeWidgetItem item = null;
		while (!stack.isEmpty()) {
			currentRow = stack.pop();
			item = getChildWidgetItem(item, currentRow.getRow());
		}

		item.setText(column, newText);
		recursiveUpdate(row);
	}

	public TreeIndex getCurrentTreeIndex() {
		final Stack<Integer> stack = new Stack<Integer>();
		
		QModelIndex index = currentIndex();
		while (index != null) {
			stack.push(index.row());
			index = index.parent();
		}

		TreeIndex treeIndex = null;
		while (!stack.isEmpty()) {
			treeIndex = new TreeIndex(stack.pop(), treeIndex);
		}
		
		return treeIndex;
	}

	public TreeIndex getTreeIndex(final QTreeWidgetItem item) {
		final Stack<Integer> stack = new Stack<Integer>();
		
		QTreeWidgetItem current = item;
		while (current != null) {
			if (current.parent() != null) {
				stack.push(current.parent().indexOfChild(current));
			} else {
				stack.push(indexOfTopLevelItem(current));
			}

			current = current.parent();
		}

		TreeIndex result = null;
		while (!stack.isEmpty()) {
			result = new TreeIndex(stack.pop(), result);
		}
		
		return result;
	}
	
	public QTreeWidgetItem getWidgetItem(final TreeIndex index) {
		final Stack<Integer> stack = new Stack<Integer>();
		
		TreeIndex current = index;
		while (current != null) {
			stack.push(current.getRow());
			current = current.getParent();
		}

		QTreeWidgetItem result = null;
		while (!stack.isEmpty()) {
			if (result == null) {
				result = topLevelItem(stack.pop());
			} else {
				result = result.child(stack.pop());
			}
		}
		
		return result;
	}

	@Override
	public void childMoved(final TreeIndex sourceIndex, final TreeIndex targetIndex) {
		final TreeIndex targetParent = targetIndex.getParent();
		final TreeIndex sourceParent = sourceIndex.getParent();
		
		final QTreeWidgetItem item = getWidgetItem(sourceIndex);
		
		final boolean isSelected = (currentItem() == item);
		final boolean isExpanded = item.isExpanded();
		
		final QTreeWidgetItem sourceParentItem = getWidgetItem(sourceParent);
		final QTreeWidgetItem parentItem = getWidgetItem(targetParent);
		
		if (sourceParentItem != null) {
			sourceParentItem.removeChild(item);
		} else {
			takeTopLevelItem(sourceIndex.getRow());
		}
		
		if (parentItem == null) {
			insertTopLevelItem(targetIndex.getRow(), item);
		} else {
			parentItem.insertChild(targetIndex.getRow(), item);
		}

		item.setExpanded(isExpanded);
		if (isSelected) {
			setCurrentItem(item);
			notifySelectionChanged(targetIndex, model.getData(targetIndex));
		}
		
		recursiveUpdate(sourceParent);
		recursiveUpdate(targetParent);
	}
	
	@Override
	public void childInserted(final TreeIndex parent, final int row) {
		final QTreeWidgetItem parentItem = getWidgetItem(parent);

		addTreeWidgetItem(parentItem, parent, row);
		
		recursiveUpdate(new TreeIndex(row, parent));
	}

	@Override
	public void childRemoved(final TreeIndex parent, final int row) {
		final QTreeWidgetItem parentItem = getWidgetItem(parent);
		final QTreeWidgetItem child;
		if (parentItem == null) {
			child = topLevelItem(row);
		} 
		else {
			child = parentItem.child(row);
		}
		
		this.setUpdatesEnabled(false);
		if (parentItem == null) {
			takeTopLevelItem(row);
		} else {
			parentItem.removeChild(child);
		}
		this.setUpdatesEnabled(true);
		
		recursiveUpdate(parent);

		currentItemChangedEvent(currentItem(), null);
	}
	
	@Override
	public void contextMenuEvent(final QContextMenuEvent event) {
		notifyContextMenuTriggered(event.globalPos());		
    }	
	
	@Override
	public List<String> mimeTypes()
	{
		if (model != null) {
			return model.getMimeTypes();
		}
		
		return new ArrayList<String>();
	}
	
	
	protected void mousePressEvent(final QMouseEvent event)
    {
		super.mousePressEvent(event);
		if (event.button().equals(Qt.MouseButton.LeftButton)) {
			draggingIndex = null;
			draggingObject = null;
            dragStartPosition = event.pos();
		}
    }
	
	protected void mouseMoveEvent(final QMouseEvent event)
    {
        if (!(event.buttons().isSet(Qt.MouseButton.LeftButton))) {
            return;
        }
        
        if ((event.pos().subtract(dragStartPosition)).manhattanLength() < QApplication.startDragDistance()) {
            return;
        }

        if (draggingIndex == null) {
        	final QTreeWidgetItem item = this.itemAt(dragStartPosition);
        	draggingIndex = getTreeIndex(item);
    	    draggingObject = model.getData(draggingIndex);
        }
        
	    if (!model.isDragAllowed(draggingIndex, draggingObject)) {
	    	return;
	    }

    	final QDrag drag = new QDrag(this);
	    final QMimeData mimeData = new QMimeData();
	    mimeData.setData(model.getMime(draggingObject.getClass()), new QByteArray("data"));
        drag.setMimeData(mimeData);

	    drag.exec(Qt.DropAction.CopyAction, Qt.DropAction.MoveAction);
    }
	
	@Override
	public Qt.DropActions supportedDropActions()
	{
	    return new Qt.DropActions(Qt.DropAction.MoveAction);
	}
	
	protected void dragEnterEvent(final QDragEnterEvent event)
	{
		if (event.source() != this) {
			return;
		}
		
		final QTreeWidgetItem item = this.itemAt(event.pos());
		final TreeIndex index = getTreeIndex(item);
		final Object data = model.getData(index);
		
		if (data == null) {
			return;
		}
		
		if (model.isDragAllowed(index, data)) {
			event.acceptProposedAction();
		}
	}
	
	protected void startTimer(final QTreeWidgetItem item)
	{
		if (timer.isActive()) {
			timer.stop();
		}

		mouseOverItem = item;
		
		if ((item != null) && (item.childCount() > 0)) {
			final int delay;
			if (item.isExpanded()) {
				delay = AUTOCLOSEDELAY;
			} else {
				delay = AUTOEXPANDDELAY;
			}
			timer.start(delay);
		}
	}
	
	protected void timeout()
	{
		timer.stop();
		mouseOverItem.setExpanded(!mouseOverItem.isExpanded());
	}
	
	protected void dragMoveEvent(final QDragMoveEvent event)
    {
		final QTreeWidgetItem item = this.itemAt(event.pos());
		startTimer(item);
		if (item != null) {
			final TreeIndex index = getTreeIndex(item);
			final Object target = model.getData(index);
			event.setAccepted(model.isDropAllowed(draggingObject, index, target));
		} else {
			event.setAccepted(false);
		}
		// TODO: check scroll => auto scroll
    }	
	
	protected void dropEvent(final QDropEvent event)
	{
		timer.stop();
		
		if (event.source() != this) {
			return;
		}
		
		if (event.proposedAction().equals(Qt.DropAction.MoveAction)) {
			final QTreeWidgetItem item = this.itemAt(event.pos());
			if (item == null) {
				return;
			}
			
			final TreeIndex index = getTreeIndex(item);
			final Object target = model.getData(index);

			this.setUpdatesEnabled(false);
			model.dropItem(draggingIndex, draggingObject, index, target);
			this.setUpdatesEnabled(true);
		}
  }

	@Override
	public void selectChild(final TreeIndex index) {
		final QTreeWidgetItem item = getWidgetItem(index);
		QTreeWidgetItem parent = item.parent();
		while (parent != null) {
			parent.setExpanded(true);
			parent = parent.parent();
		}
		setCurrentItem(item);
	}


}
